// BattLineSvc
// Copyright (C) 2009, Avian Waves Software
// SEE LICENSE.TXT for software licensing information
   
#include "stdafx.h"
#include "BattLineSvc.h"

#define MAX_LOADSTRING 100
#define REGISTRY_SETTINGS _T("\\Software\\Avian Waves\\BattLineSvc")

// ************************************************************************
// Global variables
// ************************************************************************
TCHAR g_szTitle[MAX_LOADSTRING] = _T("");
TCHAR g_szBattWindowClass[MAX_LOADSTRING] = _T("BattLineSvcBattClass");
HINSTANCE g_hInstance = NULL;
HANDLE g_hThread = NULL;
HANDLE g_hbThread = NULL;
HWND g_hTaskBar = NULL;
WNDPROC g_origWndProc = NULL;
HWND g_batthwnd = NULL;
int g_width = -1;
int g_leftLineOffset = 0;
int g_rightLineOffset = 0;
UINT g_battTimerProc = 0;
UINT g_battTimerNowProc = 0;
int g_nowRepeat = 0;
float g_battPercent = 1.0;
int g_battdcwidth = 0;
int g_battdcheight = 0;
//HREGNOTIFY g_notificationHandle = NULL;
HANDLE g_mq = NULL;
HANDLE g_pn = NULL;
int g_ppn = 0;
BOOL g_isOnAC = FALSE;

// Registry settings
TCHAR g_taskbarClass[MAX_LOADSTRING] = _T("HHTaskBar");
UINT g_battTimerInterval = 0;
int g_height = 2;
int g_leftOffset = 0;
int g_rightOffset = 0;
float g_lowPercent = 0.30;
float g_criticalPercent = 0.15;
DWORD g_alwaysUseDoubleHour = 0;
DWORD g_showLeftLine = FALSE;
DWORD g_showRightLine = FALSE;
DWORD g_showBottomLine = FALSE;
COLORREF g_cFgSolidNormal = RGB(0,180,0);
COLORREF g_cBgSolidNormal = RGB(0,75,0);
COLORREF g_cFgSolidLow = RGB(180,180,0);
COLORREF g_cBgSolidLow = RGB(75,75,0);
COLORREF g_cFgSolidCritical = RGB(180,0,0);
COLORREF g_cBgSolidCritical = RGB(75,0,0);
COLORREF g_cFgSolidAC = RGB(140,140,180);
COLORREF g_cBgSolidAC = RGB(55,55,75);
COLORREF g_cLeftLine = RGB(0,0,0);
COLORREF g_cRightLine = RGB(0,0,0);
COLORREF g_cBottomLine = RGB(0,0,0);

// ************************************************************************
// Forward declarations of functions included in this code module:
// ************************************************************************
ATOM RegisterBattClass(HINSTANCE, LPTSTR);
BOOL InitInstance();
void CreateBattWindow();
void DestroyBattWindow();
LRESULT CALLBACK BattWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK TaskBarWndProc(HWND, UINT, WPARAM, LPARAM);
void CleanUp();
BOOL BattLineMain();
void DrawBattWindow();
void RefreshBatteryStatus();
BOOL ResizeBattWindow();
BOOL ResizeBattWindow(BOOL);
void NotificationCallBack(HREGNOTIFY, DWORD, const PBYTE, const UINT);
unsigned long __cdecl PowerStateThread(LPVOID pParam);

// ************************************************************************
// Entry point
// ************************************************************************
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
  if (g_hInstance == NULL)
  {
    g_hInstance = (HINSTANCE) hModule;
  }

  switch (ul_reason_for_call)
  {
  case DLL_PROCESS_ATTACH:
  case DLL_THREAD_ATTACH:
  case DLL_THREAD_DETACH:
  case DLL_PROCESS_DETACH:
    break;
  }
  return TRUE;
}

// ************************************************************************
// Service Controllers and Timers
// ************************************************************************
void CALLBACK battTimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
{
  RefreshBatteryStatus();
}

void CALLBACK battTimerNowProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
{
  if (g_nowRepeat <= 0)
  {
    if (g_battTimerNowProc) KillTimer(0, g_battTimerNowProc);
    g_battTimerNowProc = 0;
  }

  g_nowRepeat --;
  RefreshBatteryStatus();
}

unsigned long __cdecl MyControllingFunction(LPVOID pParam)
{
  if (!BattLineMain())
  {
    CleanUp();
  }

  return 0;
}

void CleanUp()
{
  DestroyBattWindow();
  if (g_hbThread) TerminateThread(g_hbThread, 1);
  if (g_pn) StopPowerNotifications(g_pn);
  if (g_mq) CloseMsgQueue(g_mq);
  if (g_battTimerProc) KillTimer(0, g_battTimerProc);
  if (g_battTimerNowProc) KillTimer(0, g_battTimerNowProc);
  SetWindowLong(g_hTaskBar, GWL_WNDPROC, (LONG)g_origWndProc);
  if (g_hInstance) UnregisterClass(g_szBattWindowClass, g_hInstance);
  if (g_ppn) PowerPolicyNotify(PPN_UNATTENDEDMODE, 0);
//  if (g_notificationHandle) RegistryCloseNotification(g_notificationHandle);
  g_hbThread = NULL;
  g_pn = NULL;
  g_mq = NULL;
  g_battTimerProc = 0;
  g_battTimerNowProc = 0;
  g_origWndProc = NULL;
  g_batthwnd = NULL;
  g_hInstance = NULL;
  g_hTaskBar = NULL;
  g_ppn = 0;
//  g_notificationHandle = NULL;
}

// ************************************************************************
// Service Events
// ************************************************************************
DWORD BLS_Close(DWORD dwData)
{
  return 0;
}

DWORD BLS_Deinit(DWORD dwData)
{
  CleanUp();

  if (g_hThread) TerminateThread(g_hThread, 1);
  g_hThread = NULL;

  return 1;
}

DWORD BLS_Init(DWORD dwData)
{
  g_hThread = CreateThread(0, 0, MyControllingFunction, 0, 0, 0);

  return 1;
}

DWORD BLS_IOControl(DWORD dwData, DWORD dwCode, PBYTE pBufIn, DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut)
{
  return 1;
}

DWORD BLS_Open(DWORD dwData, DWORD dwAccess, DWORD dwShareMode)
{
  return 0;
}

DWORD BLS_Read(DWORD dwData, LPVOID pBuf, DWORD dwLen)
{
  return 0;
}

DWORD BLS_Seek(DWORD dwData, long pos, DWORD type)
{
  return 0;
}

DWORD BLS_Write(DWORD dwData, LPCVOID pInBuf, DWORD dwInLen)
{
  return 0;
}

// ************************************************************************
// Service Work
// ************************************************************************

void LoadRegistrySettings()
{
  HKEY hk;
  DWORD disp;
  DWORD maxBytes;
  DWORD temp;
  RegCreateKeyEx(HKEY_CURRENT_USER, REGISTRY_SETTINGS, 0, _T(""), 0, 0, NULL, &hk, &disp);

  // Timer interval
  maxBytes = sizeof(g_battTimerInterval);
  if (RegQueryValueEx(hk, _T("TimerIntervalSeconds"), 0, NULL, (LPBYTE) &g_battTimerInterval, &maxBytes))
  {
    g_battTimerInterval = 0;
    RegSetValueEx(hk, _T("TimerIntervalSeconds"), 0, REG_DWORD, (LPBYTE) &g_battTimerInterval, maxBytes);
  }
  g_battTimerInterval = g_battTimerInterval * 1000;

  // Taskbar Class
  maxBytes = MAX_LOADSTRING * sizeof(wchar_t);
  if (RegQueryValueEx(hk, _T("TaskbarClass"), 0, NULL, (LPBYTE) g_taskbarClass, &maxBytes))
  {
    _tcscpy(g_taskbarClass, _T("HHTaskBar"));
    RegSetValueEx(hk, _T("TaskbarClass"), 0, REG_SZ, (LPBYTE) g_taskbarClass, maxBytes);
  }

  // Height
  maxBytes = sizeof(g_height);
  if (RegQueryValueEx(hk, _T("Height"), 0, NULL, (LPBYTE) &g_height, &maxBytes))
  {
    g_height = 2;
    RegSetValueEx(hk, _T("Height"), 0, REG_DWORD, (LPBYTE) &g_height, maxBytes);
  }

  // Left-side offset
  maxBytes = sizeof(g_leftOffset);
  if (RegQueryValueEx(hk, _T("LeftOffset"), 0, NULL, (LPBYTE) &g_leftOffset, &maxBytes))
  {
    g_leftOffset = 0;
    RegSetValueEx(hk, _T("LeftOffset"), 0, REG_DWORD, (LPBYTE) &g_leftOffset, maxBytes);
  }

  // Right-side offset
  maxBytes = sizeof(g_rightOffset);
  if (RegQueryValueEx(hk, _T("RightOffset"), 0, NULL, (LPBYTE) &g_rightOffset, &maxBytes))
  {
    g_rightOffset = 0;
    RegSetValueEx(hk, _T("RightOffset"), 0, REG_DWORD, (LPBYTE) &g_rightOffset, maxBytes);
  }

  // Low Percent
  maxBytes = sizeof(temp);
  if (RegQueryValueEx(hk, _T("LowPercent"), 0, NULL, (LPBYTE) &temp, &maxBytes))
  {
    temp = 30;
    RegSetValueEx(hk, _T("LowPercent"), 0, REG_DWORD, (LPBYTE) &temp, maxBytes);
  }
  g_lowPercent = ((float) temp) / ((float) 100);

  // Critical Percent
  maxBytes = sizeof(temp);
  if (RegQueryValueEx(hk, _T("CriticalPercent"), 0, NULL, (LPBYTE) &temp, &maxBytes))
  {
    temp = 15;
    RegSetValueEx(hk, _T("CriticalPercent"), 0, REG_DWORD, (LPBYTE) &temp, maxBytes);
  }
  g_criticalPercent = ((float) temp) / ((float) 100);

  // Show Left Line
  maxBytes = sizeof(g_showLeftLine);
  if (RegQueryValueEx(hk, _T("ShowLeftLine"), 0, NULL, (LPBYTE) &g_showLeftLine, &maxBytes))
  {
    g_showLeftLine = 0;
    RegSetValueEx(hk, _T("ShowLeftLine"), 0, REG_DWORD, (LPBYTE) &g_showLeftLine, maxBytes);
  }

  // Show Right Line
  maxBytes = sizeof(g_showRightLine);
  if (RegQueryValueEx(hk, _T("ShowRightLine"), 0, NULL, (LPBYTE) &g_showRightLine, &maxBytes))
  {
    g_showRightLine = 0;
    RegSetValueEx(hk, _T("ShowRightLine"), 0, REG_DWORD, (LPBYTE) &g_showRightLine, maxBytes);
  }

  // Show Bottom Line
  maxBytes = sizeof(g_showBottomLine);
  if (RegQueryValueEx(hk, _T("ShowBottomLine"), 0, NULL, (LPBYTE) &g_showBottomLine, &maxBytes))
  {
    g_showBottomLine = 0;
    RegSetValueEx(hk, _T("ShowBottomLine"), 0, REG_DWORD, (LPBYTE) &g_showBottomLine, maxBytes);
  }

  // Foreground color in "Solid" mode, normal percent
  maxBytes = sizeof(g_cFgSolidNormal);
  if (RegQueryValueEx(hk, _T("ColorForegroundSolidNormal"), 0, NULL, (LPBYTE) &g_cFgSolidNormal, &maxBytes))
  {
    g_cFgSolidNormal = (DWORD) RGB(0,180,0);
    RegSetValueEx(hk, _T("ColorForegroundSolidNormal"), 0, REG_DWORD, (LPBYTE) &g_cFgSolidNormal, maxBytes);
  }

  // Background color in "Solid" mode, normal percent
  maxBytes = sizeof(g_cBgSolidNormal);
  if (RegQueryValueEx(hk, _T("ColorBackgroundSolidNormal"), 0, NULL, (LPBYTE) &g_cBgSolidNormal, &maxBytes))
  {
    g_cBgSolidNormal = (DWORD) RGB(0,75,0);
    RegSetValueEx(hk, _T("ColorBackgroundSolidNormal"), 0, REG_DWORD, (LPBYTE) &g_cBgSolidNormal, maxBytes);
  }

  // Foreground color in "Solid" mode, low percent
  maxBytes = sizeof(g_cFgSolidLow);
  if (RegQueryValueEx(hk, _T("ColorForegroundSolidLow"), 0, NULL, (LPBYTE) &g_cFgSolidLow, &maxBytes))
  {
    g_cFgSolidLow = (DWORD) RGB(180,180,0);
    RegSetValueEx(hk, _T("ColorForegroundSolidLow"), 0, REG_DWORD, (LPBYTE) &g_cFgSolidLow, maxBytes);
  }

  // Background color in "Solid" mode, low percent
  maxBytes = sizeof(g_cBgSolidLow);
  if (RegQueryValueEx(hk, _T("ColorBackgroundSolidLow"), 0, NULL, (LPBYTE) &g_cBgSolidLow, &maxBytes))
  {
    g_cBgSolidLow = (DWORD) RGB(75,75,0);
    RegSetValueEx(hk, _T("ColorBackgroundSolidLow"), 0, REG_DWORD, (LPBYTE) &g_cBgSolidLow, maxBytes);
  }

  // Foreground color in "Solid" mode, critical percent
  maxBytes = sizeof(g_cFgSolidCritical);
  if (RegQueryValueEx(hk, _T("ColorForegroundSolidCritical"), 0, NULL, (LPBYTE) &g_cFgSolidCritical, &maxBytes))
  {
    g_cFgSolidCritical = (DWORD) RGB(180,0,0);
    RegSetValueEx(hk, _T("ColorForegroundSolidCritical"), 0, REG_DWORD, (LPBYTE) &g_cFgSolidCritical, maxBytes);
  }

  // Background color in "Solid" mode, critical percent
  maxBytes = sizeof(g_cBgSolidCritical);
  if (RegQueryValueEx(hk, _T("ColorBackgroundSolidCritical"), 0, NULL, (LPBYTE) &g_cBgSolidCritical, &maxBytes))
  {
    g_cBgSolidCritical = (DWORD) RGB(75,0,0);
    RegSetValueEx(hk, _T("ColorBackgroundSolidCritical"), 0, REG_DWORD, (LPBYTE) &g_cBgSolidCritical, maxBytes);
  }

  // Foreground color in "Solid" mode, charging
  maxBytes = sizeof(g_cFgSolidAC);
  if (RegQueryValueEx(hk, _T("ColorForegroundSolidCharging"), 0, NULL, (LPBYTE) &g_cFgSolidAC, &maxBytes))
  {
    g_cFgSolidAC = (DWORD) RGB(140,140,180);
    RegSetValueEx(hk, _T("ColorForegroundSolidCharging"), 0, REG_DWORD, (LPBYTE) &g_cFgSolidAC, maxBytes);
  }

  // Background color in "Solid" mode, charging
  maxBytes = sizeof(g_cBgSolidAC);
  if (RegQueryValueEx(hk, _T("ColorBackgroundSolidCharging"), 0, NULL, (LPBYTE) &g_cBgSolidAC, &maxBytes))
  {
    g_cBgSolidAC = (DWORD) RGB(55,55,75);
    RegSetValueEx(hk, _T("ColorBackgroundSolidCharging"), 0, REG_DWORD, (LPBYTE) &g_cBgSolidAC, maxBytes);
  }

  // Color of the left line
  maxBytes = sizeof(g_cLeftLine);
  if (RegQueryValueEx(hk, _T("ColorLeftLine"), 0, NULL, (LPBYTE) &g_cLeftLine, &maxBytes))
  {
    g_cLeftLine = (DWORD) RGB(0,0,0);
    RegSetValueEx(hk, _T("ColorLeftLine"), 0, REG_DWORD, (LPBYTE) &g_cLeftLine, maxBytes);
  }

  // Color of the Right line
  maxBytes = sizeof(g_cRightLine);
  if (RegQueryValueEx(hk, _T("ColorRightLine"), 0, NULL, (LPBYTE) &g_cRightLine, &maxBytes))
  {
    g_cRightLine = (DWORD) RGB(0,0,0);
    RegSetValueEx(hk, _T("ColorRightLine"), 0, REG_DWORD, (LPBYTE) &g_cRightLine, maxBytes);
  }

  // Color of the bottom line
  maxBytes = sizeof(g_cBottomLine);
  if (RegQueryValueEx(hk, _T("ColorBottomLine"), 0, NULL, (LPBYTE) &g_cBottomLine, &maxBytes))
  {
    g_cBottomLine = (DWORD) RGB(0,0,0);
    RegSetValueEx(hk, _T("ColorBottomLine"), 0, REG_DWORD, (LPBYTE) &g_cBottomLine, maxBytes);
  }

  RegCloseKey(hk);
}

BOOL BattLineMain()
{
  MSG msg;

  // Load the registry
  LoadRegistrySettings();

  // For safety, we wait 30 seconds for the system start up before even trying
  while (GetTickCount() < 30000) ::Sleep(1000);
  
  // Subclass the taskbar
  do
  {
    g_hTaskBar = FindWindow(g_taskbarClass, NULL);
    if (!g_hTaskBar)
    {
      if (!g_origWndProc) ::Sleep(1000); // Window is not ready to subclass, sleep for a second
    }
    else
    {
      g_origWndProc = (WNDPROC) SetWindowLong(g_hTaskBar, GWL_WNDPROC, (LONG)TaskBarWndProc);
    }
  } while (!g_origWndProc || !g_hTaskBar); // Keep retrying until we found the window and are subclassed

  // Perform window (and other misc) initialization -- this is safe now since the class we are subclassing is definitely initialized
  if (!InitInstance()) return FALSE;

  // Create timer
  if (g_battTimerInterval > 0)
  {
    g_battTimerProc = SetTimer(0, 0, g_battTimerInterval, battTimerProc);
  }

  // Get initial battery life
  RefreshBatteryStatus();

  // Reset / resize windows (if necessary)
  ResizeBattWindow();
  
  // Register a callback for when the settings change
//  RegistryNotifyCallback(HKEY_CURRENT_USER, REGISTRY_SETTINGS, _T("_ConfigChanged"), NotificationCallBack, NULL, NULL, &g_notificationHandle);

  // Start the power notification thread
  g_hbThread = CreateThread(0, 0, PowerStateThread, 0, 0, 0);

  // Main message loop:
  while (GetMessage(&msg, NULL, 0, 0)) 
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }

  return TRUE;
}

ATOM RegisterBattClass(HINSTANCE hInstance, LPTSTR szWindowClass)
{
  WNDCLASS wc;

  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = BattWndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = 0;
  wc.hCursor       = 0;
  wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
  wc.lpszMenuName  = 0;
  wc.lpszClassName = szWindowClass;

  return RegisterClass(&wc);
}

BOOL InitInstance()
{
  // SHInitExtraControls should be called once during your application's initialization to initialize any
  // of the device specific controls such as CAPEDIT and SIPPREF.
  SHInitExtraControls();

  if (!RegisterBattClass(g_hInstance, g_szBattWindowClass))
  {
    return FALSE;
  }

  CreateBattWindow();

  if (!g_batthwnd)
  {
    return FALSE;
  }

  return TRUE;
}

void CreateBattWindow()
{
  g_width = GetSystemMetrics(SM_CXSCREEN);
  g_batthwnd = CreateWindowEx(WS_EX_TOPMOST, g_szBattWindowClass, g_szTitle, WS_CHILD | WS_VISIBLE, g_leftOffset, 0, g_width - g_rightOffset, g_height, g_hTaskBar, NULL, g_hInstance, NULL);

  if (g_batthwnd)
  {
    ShowWindow(g_batthwnd, SW_SHOWNORMAL);
    SetWindowPos(g_batthwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
    UpdateWindow(g_batthwnd);
  }
}

void DestroyBattWindow()
{
  if (g_batthwnd) DestroyWindow(g_batthwnd);
  g_batthwnd = 0;
}

//void ReloadBattWindow()
//{
//  DestroyBattWindow();
//  LoadRegistrySettings();
//  CreateBattWindow();
//  RefreshBatteryStatus();
//  ResizeBattWindow(TRUE);
//}

void DrawBattWindow()
{
  PAINTSTRUCT ps;
  HDC hdc;
  HDC bufdc;
  HBITMAP bufbmp;
  HGDIOBJ oldbmp;
  HBRUSH sb;
  RECT rc;

  if (g_batthwnd && !ResizeBattWindow())
  {
    // Select color
    COLORREF cfg;
    COLORREF cbg;
    int battPortionWidth = g_battdcwidth - (g_leftLineOffset + g_rightLineOffset);

    if (g_isOnAC)
    {
      cfg = g_cFgSolidAC;
      cbg = g_cBgSolidAC;
    }
    else if (g_battPercent <= g_criticalPercent)
    {
      cfg = g_cFgSolidCritical;
      cbg = g_cBgSolidCritical;
    }
    else if (g_battPercent <= g_lowPercent)
    {
      cfg = g_cFgSolidLow;
      cbg = g_cBgSolidLow;
    }
    else
    {
      cfg = g_cFgSolidNormal;
      cbg = g_cBgSolidNormal;
    }

    // Start painting
    hdc = BeginPaint(g_batthwnd, &ps);

    // Create backbuffer
    bufdc = CreateCompatibleDC(hdc);
    bufbmp = CreateCompatibleBitmap(hdc, g_battdcwidth, g_battdcheight);
    oldbmp = SelectObject(bufdc, bufbmp);

    // Paint forefround level
    sb = CreateSolidBrush(cfg);
    rc.left = g_leftLineOffset; rc.top = 0; rc.right = (int)(battPortionWidth * g_battPercent) + g_leftLineOffset; rc.bottom = g_height;
    FillRect(bufdc, &rc, sb);
    DeleteObject(sb);

    // Paint background remainder
    if (rc.right < battPortionWidth)
    {
      sb = CreateSolidBrush(cbg);
      rc.left = rc.right; rc.right = g_battdcwidth;
      FillRect(bufdc, &rc, sb);
      DeleteObject(sb);
    }

    // Paint left line
    if (g_showLeftLine)
    {
      sb = CreateSolidBrush(g_cLeftLine);
      rc.left = 0; rc.top = 0; rc.right = 1; rc.bottom = g_height + 1;
      FillRect(bufdc, &rc, sb);
      DeleteObject(sb);
    }

    // Paint right line
    if (g_showRightLine)
    {
      sb = CreateSolidBrush(g_cRightLine);
      rc.left = g_battdcwidth - 1; rc.top = 0; rc.right = g_battdcwidth; rc.bottom = g_height + 1;
      FillRect(bufdc, &rc, sb);
      DeleteObject(sb);
    }

    // Paint bottom line
    if (g_showBottomLine)
    {
      sb = CreateSolidBrush(g_cBottomLine);
      rc.left = 0; rc.top = g_height; rc.right = g_battdcwidth; rc.bottom = g_height + 1;
      FillRect(bufdc, &rc, sb);
      DeleteObject(sb);
    }

    // copy backbuffer to screen
    BitBlt(hdc, 0, 0, g_battdcwidth, g_battdcheight, bufdc, 0, 0, SRCCOPY);

    // Clean up
    SelectObject(bufdc, oldbmp);
    DeleteObject(bufbmp);
    DeleteDC(bufdc);

    // End painting
    EndPaint(g_batthwnd, &ps);
  }
}

LRESULT CALLBACK BattWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  static SHACTIVATEINFO s_sai;
  static int newx;
  static int newy;

  switch (message) 
  {
  case WM_LBUTTONDOWN:
  case WM_LBUTTONUP:
  case WM_RBUTTONDOWN:
  case WM_RBUTTONUP:
  case WM_MOUSEMOVE:
    // This passes any touch screen activity up to the actual task bar
    newx = LOWORD(lParam) + g_leftOffset;
    newy = HIWORD(lParam);
    PostMessage(g_hTaskBar, message, wParam, MAKELPARAM(MAKEWPARAM(newx,newy),0));
    break;

  case WM_PAINT:
    DrawBattWindow();
    break;

  case WM_ACTIVATE:
    SHHandleWMActivate(hWnd, wParam, lParam, &s_sai, FALSE);
    break;

  case WM_SETTINGCHANGE:
    SHHandleWMSettingChange(hWnd, wParam, lParam, &s_sai);
    break;

  default:
    return DefWindowProc(hWnd, message, wParam, lParam);
  }

  return 0;
}

LRESULT CALLBACK TaskBarWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  switch (message)
  {
  case WM_SIZE:
    ResizeBattWindow();
    break;
  }

  return CallWindowProc(g_origWndProc, g_hTaskBar, message, wParam, lParam);
}

void RefreshBatteryStatus()
{
  SYSTEM_POWER_STATUS_EX status;
  float newBatt = 0;
  BOOL update = FALSE;

  if (g_batthwnd)
  {
    if (GetSystemPowerStatusEx(&status,TRUE))
    {
      if (status.ACLineStatus > 0x00 && status.ACLineStatus < 0xFF)
      {
        if (!g_isOnAC)
        {
          g_isOnAC = TRUE;
          update = TRUE;
        }
      }
      else if (g_isOnAC)
      {
        g_isOnAC = FALSE;
        update = TRUE;
      }

      if (((status.BatteryFlag & BATTERY_FLAG_UNKNOWN) != BATTERY_FLAG_UNKNOWN) && status.BatteryLifePercent <= 100)
      {
        newBatt = (float) status.BatteryLifePercent / (float) 100;
        if (newBatt != g_battPercent)
        {
          g_battPercent = newBatt;
          update = TRUE;
        }
      }
    }

    if (update)
    {
      InvalidateRect(g_batthwnd, NULL, TRUE);
      UpdateWindow(g_batthwnd);
    }
  }
}

BOOL ResizeBattWindow()
{
  return ResizeBattWindow(FALSE);
}

BOOL ResizeBattWindow(BOOL force)
{
  static int lastWidth = -1;

  if (g_batthwnd)
  {
    if (force) lastWidth = -1;

    // Get new screen dimensions, to see if it changed
    int newWidth = GetSystemMetrics(SM_CXSCREEN); 

    // Do we resize/move the window?
    if (newWidth != lastWidth)
    {
      lastWidth = newWidth;
      g_width = newWidth;
      g_battdcwidth = g_width - (g_leftOffset + g_rightOffset);

      if (g_showLeftLine)
      {
        g_leftLineOffset = 1;
      }
      else
      {
        g_leftLineOffset = 0;
      }

      if (g_showRightLine)
      {
        g_rightLineOffset = 1;
      }
      else
      {
        g_rightLineOffset = 0;
      }

      if (g_showBottomLine)
      {
        g_battdcheight = g_height + 1;
      }
      else
      {
        g_battdcheight = g_height;
      }

      MoveWindow(g_batthwnd, g_leftOffset, 0, g_battdcwidth, g_battdcheight, TRUE);

      return TRUE;
    }
  }

  return FALSE;
}

//void NotificationCallBack(HREGNOTIFY, DWORD, const PBYTE, const UINT)
//{
//  ReloadBattWindow();
//}

unsigned long __cdecl PowerStateThread(LPVOID pParam)
{
  DWORD buf[16];
  DWORD numberRead;
  DWORD flags;
  MSGQUEUEOPTIONS mqopt;
  mqopt.dwFlags = 0;
  mqopt.dwMaxMessages = 20;
  mqopt.cbMaxMessage = 50;
  mqopt.bReadAccess = TRUE;
  mqopt.dwSize = sizeof(mqopt);

  PowerPolicyNotify(PPN_UNATTENDEDMODE, 1);
  g_ppn = 1;

  g_mq = CreateMsgQueue(L"AvianWavesBattLineSvcPwrNotifQueue", &mqopt);
  if (g_mq)
  {
    g_pn = RequestPowerNotifications(g_mq, PBT_TRANSITION | PBT_POWERSTATUSCHANGE | PBT_POWERINFOCHANGE);
    if (g_pn)
    {
      while (WaitForSingleObject(g_mq, INFINITE) == WAIT_OBJECT_0) 
      {
        // Eat the power notification
        ReadMsgQueue(g_mq, &buf, sizeof(DWORD) * 16, &numberRead, -1, &flags);

        // Refresh the battery on any power change (we repeat several times since sometimes the battery level does not refresh in sync with the status change
        RefreshBatteryStatus();
        g_nowRepeat = 1;
        if (g_battTimerNowProc == 0)
        {
          g_battTimerNowProc = SetTimer(0, 0, 2000, battTimerNowProc);
        }
      }
    }
    g_pn = NULL;
  }
  return 0;
}

